package com.lin.demo.utils;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.lin.demo.annotations.FilterField;
import com.lin.demo.enums.WrapperScheme;
import com.lin.demo.model.QueryObject;
import org.springframework.util.StringUtils;

import java.lang.reflect.Field;

/**
 * @Author zimingl
 * @Date 2023/3/12 22:00
 * @Description: TODO
 */
public class QueryWrapperFilterUtil<T, E> {
    private QueryWrapperFilterUtil() {
    }

    public static QueryWrapperFilterUtil getInstance() {
        return new QueryWrapperFilterUtil();
    }

    private QueryWrapper<T> wrapper;

    private QueryObject qo;

    /**
     * 此方法通过反射QO查询对象获取字段和字段上注解来封装调用wrapper对应的方法
     * 通过放射获取字段的名称和值 对{@link FilterField}的存在进行判断
     * 对{@FieldName name}属性的存在进行判断决定调用的方法的参数
     *
     * @param qo      持有过滤条件的对象
     * @param wrapper 过滤对象
     * @return 封装了对应过滤条件的过滤对象
     */
    public QueryWrapper<T> wrapper(QueryObject qo, QueryWrapper<T> wrapper) {
        this.wrapper = wrapper;
        this.qo = qo;
        Class<? extends QueryObject> clz = qo.getClass();
        Field[] fields = clz.getDeclaredFields();
        for (Field field : fields) {
            field.setAccessible(true);
            //调用handle方法获取当前字段的字段值
            E param = this.handle(field);
            //调用transformColumn方法获取字段的名称
            String name = this.transformColumn(field.getName());
            //或者字段上的注解
            FilterField annotation = field.getAnnotation(FilterField.class);
            //判断字段上是否拥有注解
            if (annotation == null) {
                wrapper = wrapper.eq(this.judgementCondition(param), name, param);
                continue;
            }
            //获取注解中的名称属性
            String[] parameter = annotation.name();
            //判断注解的type属性
            boolean bool = annotation.type().isFlag();
            //获取注解可能拥有的所有过滤方案
            WrapperScheme[] wrapperSchemes = annotation.scheme();
            //遍历封装注解的每一个过滤方案
            for (WrapperScheme wrapperScheme : wrapperSchemes) {
                Integer schemeValue = wrapperScheme.getValue();
                boolean ifName = "".equals(parameter[0].trim());
                //判断注解是否定义了自定义名称 否则采用默认字段名称转换_分隔符的字符串
                if (parameter.length == 1 && ifName) {
                    this.wrapper(schemeValue, name, param);
                    this.flag(bool, null, null);
                    continue;
                }
                //遍历注解中的每一个自定义名称
                for (int index = 0; index < parameter.length; index++) {
                    this.wrapper(schemeValue, parameter[index], param);
                    this.flag(bool, index, parameter.length - 1);
                }
            }
        }
        return wrapper;
    }

    /**
     * 通过{@link WrapperScheme}枚举中的值属性 进行判断语句确认调用方法类型
     * 如果在{@link WrapperScheme}枚举中添加了对应的枚名称类型
     * 请在此处书写对应的判断方法
     *
     * @param scheme    {@link WrapperScheme}枚举中对应名称的值
     * @param parameter {@link FilterField} 注解中对应的 name 参数
     * @param param     通过反射获取到的 {@link QueryObject} 对象中封装的参数值
     */
    private void wrapper(Integer scheme, String parameter, E param) {
        boolean flag = this.judgementCondition(param);
        switch (scheme) {
            case 1: wrapper.ge(flag, parameter, param); break;
            case 2: wrapper.le(flag, parameter, param); break;
            case 3: wrapper.like(flag, parameter, param); break;
            case 4: wrapper.orderByAsc(parameter); break;
            case 5: wrapper.orderByDesc(parameter); break;
            case 6:
                if(!flag) { break; }
                if (Boolean.parseBoolean(param.toString())) {
                    wrapper.isNotNull(parameter);
                    break;
                }
                wrapper.isNull(parameter);
                break;
            default: wrapper.eq(flag, parameter, param); break;
        }
    }

    /**
     * 这个方法是用来将 ele字符串中存在的大写字符
     * 即驼峰书写方式转化为小写并前置添加"_"符号 的分隔符号样式的列名称
     *
     * @param ele 被转换的字符串对象
     * @return 遵循了单词分隔符号的列名称
     */
    public String transformColumn(String ele) {
        StringBuffer sb = new StringBuffer();
        sb.append(ele);
        for (int i = 0; i < ele.length(); i++) {
            char c = sb.charAt(i);
            if (c < 97) {
                c = (char) (c + 32);
                sb.delete(i, i + 1);
                sb.insert(i, "_" + c);
            }
        }
        return sb.toString();
    }


    /**
     * 判断过滤查询对象中的字段是否拥有值
     *
     * @param param 字段对象
     * @return
     */
    private boolean judgementCondition(E param) {
        return param != null && StringUtils.hasLength(param.toString());
    }

    /**
     * 对可能出现的反射调用获取字段值进行异常捕捉处理
     *
     * @param field 反射获取道的字段
     * @return 反射调用获取到的对应字段的值
     */
    private E handle(Field field) {
        try {
            return (E) field.get(qo);
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 对{@link PickType} 中判断的AND与OR的拼接进行判断和方法调用
     *
     * @param flag  {@link PickType}的type默认是false
     *              如果为true则为OR方法调用
     *              只进行是否为OR的方法调用 因为wrapper默认是AND
     * @param index 查询对象中的所有字段的对应索引位置
     * @param len   查询对象中所有字段的数量 用来对应对象中所有字段的总数量
     *              警示最后一个字段并不需要在进行OR方法调用
     */
    private void flag(boolean flag, Integer index, Integer len) {
        boolean indexIf = (!flag || index != null);
        boolean lenIf = (len != null && index < len);
        if (indexIf && lenIf) {
            wrapper.or();
        }
    }

}
